(use-modules
 ;; for textual reading and writing procedures
 (ice-9 textual-ports)
 ;; not sure if needed
 (ice-9 binary-ports)
 ;; for `eof-object?`
 (ice-9 rdelim)
 ;; for keyword arguments
 (ice-9 optargs)
 ;; procedures for lists
 (srfi srfi-1)
 ;; unit testing
 (srfi srfi-64)
 ;; more hash-table procedures
 (ice-9 hash-table))

;; =================
;; HELPER PROCEDURES
;; =================

;; FILE HANDLING
(define* (get-lines-from-file file-path #:key (encoding "UTF-8"))
  ;; another common encoding is: "ISO-8859-1"
  ;; see http://www.iana.org/assignments/character-sets for more
  (define (get-lines-from-port port)
    (let ([line (get-line port)])
      (cond [(eof-object? line) '()]
            [else
             (cons line
                   (get-lines-from-port port))])))
  (call-with-input-file file-path
    (lambda (port)
      (set-port-encoding! port encoding)
      (get-lines-from-port port))))

(define (println output)
  (display (simple-format #f "~s\n" output)))

;; ============
;; ACTUAL LOGIC
;; ============
(define (find-first-repeating list-of-numbers initial-frequency)
  ;; We are using a hash-table as a set (maybe not optimal solution,
  ;; but I could not find a set data type in the Guile docs and a
  ;; hash-table should have O(1) check if something is in the
  ;; hash-table of not. In this initial hash-table we need to
  ;; initially already story the initial frequency, so that if we come
  ;; back to that initial frequency, we recognize it as already found.
  (define initial-already-found-frequencies
    (alist->hash-table `((,initial-frequency . #t))))

  (define (iter remaining-frequency-changes already-found-frequencies current-frequency)
    (cond
     ;; If the list of numbers is exhausted, restart with the full
     ;; list again, but keep the current frequency, to make any
     ;; progress. (Lets hope it will not cause an endless loop.)
     [(null? remaining-frequency-changes) (iter list-of-numbers
                                                already-found-frequencies
                                                current-frequency)]
     ;; If there is a remaining number, we need to check if the
     ;; resulting frequency is already in our set of seen frequencies.
     [(hash-ref already-found-frequencies
                (+ current-frequency (car remaining-frequency-changes))
                #f)  ; defaulting to false
      ;; This is the result we are looking for. We are done.
      (+ current-frequency (car remaining-frequency-changes))]
     ;; If the frequency has not been found before, we need to add it
     ;; to the already found frequencies and then go on in the search.
     [else
      ;; Add it to the already found frequencies.
      (hash-set! already-found-frequencies
                 (+ current-frequency (car remaining-frequency-changes))
                 #t)
      ;; Recur.
      (iter (cdr remaining-frequency-changes)
            already-found-frequencies
            (+ current-frequency (car remaining-frequency-changes)))]))

  ;; initial call
  (iter list-of-numbers
        initial-already-found-frequencies
        initial-frequency))

(define (main args)
  (let* ([initial-frequency 0]
         [lines (get-lines-from-file (cadr args))]
         [frequency-changes (map string->number lines)])
    (println (find-first-repeating frequency-changes initial-frequency))))

(main (program-arguments))

;; =====
;; TESTS
;; =====


;; Example from the docs:
;; Initialize and give a name to a simple testsuite.
(test-begin "day-01-part-02-test")
(test-group
 "day-01-part-02-test"
 (test-eqv 0 (find-first-repeating '(1 -1) 0))
 (test-eqv 0 (find-first-repeating '(1 2 -3) 0))
 (test-eqv 3 (find-first-repeating '(1 2 5 -4 -1) 0))
 (test-eqv 9 (find-first-repeating '(1 2 -5 -4 10 5 2 -1 2 -3) 0)))
(test-end "day-01-part-02-test")
